/****************************************************************************
 *
 * Copyright (C) 2003-2010 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse;

import java.io.File;
import java.io.FileInputStream;
import java.io.RandomAccessFile;
import java.util.zip.GZIPInputStream;

public abstract class NetFSERecordFactory {

    protected int typeID = -1;

    protected String typeName = null;

    public NetFSERecordFactory() {
        NetFSERecord rec = generate();
        this.typeID = rec.getTypeID();
        this.typeName = rec.getTypeName();
    }

    public NetFSERecordFactory(int typeID, String typeName) {
        this.typeID = typeID;
        this.typeName = typeName;
    }

    public static void main(String[] args) throws Exception {
        if (args.length >= 2) {
            NetFSERecordFactory rec = (NetFSERecordFactory) Class.forName(args[0]).newInstance();
            String[] args2 = new String[args.length-1];
            for (int i = 1; i < args.length; i++)
                args2[i-1] = args[i];
            rec.parse(args2);
        } // else
        // throw new
        // Exception("Invalid # of arguments. Must be 2: class name and file path.");
    }
    
    public void parse(String[] args) {
        try {
            File f = new File(args[0]);
            if (!f.exists()) {
                System.err.println("The file specified does not exist: '" + args[0] + "'.");
                throw (new Exception());
            }

            String keyword = null;

            NetFSERecord rec = generate();
            boolean isSyslog = rec.isSyslog();
            if (isSyslog)
                keyword = args[1];

            long count = 0;
            long bad = 0;
            long good = 0;
            long start = System.currentTimeMillis();
            if (f.getName().endsWith(".gz")) {
                GZIPInputStream in = new GZIPInputStream(new FileInputStream(f));
                String recordLine = getRecordLine(in);
                while (recordLine != null) {
                    try {
                        count++;
                        NetFSERecord rec2 = generate(recordLine, -1);
                        if (rec2 == null)
                            throw (new Exception("The record could not be parsed"));
                        good++;
                    } catch (Exception e) {
                        bad++;

                        System.out.println("-- Start bad record --\n" + recordLine + "\n-- End bad record --");
                    }
                    recordLine = getRecordLine(in);
                }
                in.close();
            } else {
                RandomAccessFile raf = new RandomAccessFile(f, "r");
                String recordLine = getRecordLine(raf);
                while (recordLine != null) {
                    try {

                        boolean matches = false;
                        if (isSyslog) {
                            int tagIdx = recordLine.indexOf(keyword);
                            try {
                                int tagIdx2 = recordLine.indexOf(" ", tagIdx + keyword.length());
                                String tag = recordLine.substring(tagIdx, tagIdx2);
                                // System.out.println(tag);
                                if (tag.endsWith(":")) {
                                    matches = true;
                                    recordLine = recordLine.substring(tagIdx2).trim();
                                }
                            } catch (Exception e) {
                                // e.printStackTrace();
                            }
                        }

                        if (!isSyslog || (isSyslog && matches)) {
                            count++;
                            NetFSERecord rec2 = generate(recordLine, -1);
                            if (rec2 == null)
                                throw (new Exception("The record could not be parsed"));
                            good++;
                        }
                    } catch (Exception e) {
                        bad++;
                        e.printStackTrace();
                        System.out.println("-- Start bad record --\n" + recordLine + "\n-- End bad record --");
                    }
                    recordLine = getRecordLine(raf);
                }
                raf.close();
            }
            long stop = System.currentTimeMillis();

            float sec = (float) ((stop - start) / 1000);
            if (sec == (float) 0.0)
                sec = (float) 1.0;

            System.out.println("       Records: " + NetFSEUtil.formattedInteger(count));
            System.out.println("  Good records: " + NetFSEUtil.formattedInteger(good));
            System.out.println("   Bad records: " + NetFSEUtil.formattedInteger(bad));
            System.out.println("       Seconds: " + NetFSEUtil.formattedDouble(sec));
            System.out.println("Records/Second: " + NetFSEUtil.formattedDouble(count / sec));

        } catch (Exception e) {
            System.err.println("Usage: java " + getClass().getName() + " /data/file/path");
            System.err.println("\tThe data file can be raw logs or gzipped (ending in .gz)");
            e.printStackTrace();
        }
    }

    /***************************************************************************
     * This abstract method is the parser component of Net/FSE. Each event type
     * implements this function which returns a fully-parsed nfse.NetFSERecord.
     * 
     * @param line
     *            The String containing the unparsed event.
     * @param sensor
     *            The sensor from metadata.Sensors which corresponds to this
     *            event.
     * @return A NetFSERecord instance. Before returning a parsed event, the
     *         record must be cast back into a NetFSERecord.
     * @throws Exception
     *             Any parsing failure should throw some form of Exception which
     *             will be handled by the various Net/FSE components that parse
     *             events.
     */
    public abstract NetFSERecord generate(String line, int sensor) throws Exception;

    /**
     * Returns an instance created with the record's default constructor.
     * 
     * @return An empty record.
     */
    public abstract NetFSERecord generate();

    /***************************************************************************
     * This method will return a single-line event from a gzipped data stream.
     * Bytes are read until the '\n' character is read. This method is
     * appropriate for any event type whose records are written on single lines.
     * Multi-line records need to override this function.
     * 
     * @param dataIn
     *            The gzipped data stream (ultimately a file) from which events
     *            are being read and parsed.
     * @return The unparsed event as a String.
     */
    public String getRecordLine(GZIPInputStream dataIn) {
        try {
            StringBuilder line = new StringBuilder();
            int ch = 0;
            do {
                ch = dataIn.read();
                line.append((char) ch);

            } while ((ch != -1) && ((char) ch != '\n'));
            return line.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /***************************************************************************
     * This method will return a single-line event from a RandomAccessFile using
     * the readLine() method. This method is appropriate for any event type
     * whose records are written on single lines. Multi-line records need to
     * override this function.
     * 
     * @param dataIn
     *            The file from which events are being read and parsed.
     * @return The unparsed event as a String.
     */
    public String getRecordLine(RandomAccessFile dataIn) {
        try {
            String line = dataIn.readLine();
            return line;
        } catch (Exception e) {

        }
        return null;
    }

    /***************************************************************************
     * Net/FSE handles syslog events differently from non-syslog events.
     * Typically, syslog events have timestamp or IP information in the syslog
     * header. If this method returns true, the syslog header will be maintained
     * by nfse.SyslogThread. If it returns false the header will be discarded.
     * This method should be overridden to return true for any event type that
     * requires the syslog header for obtaining field information needed by
     * Net/FSE (i.e. timestamps and IPs).
     * 
     * @return This stub method returns false, causing the syslog header to be
     *         discarded.
     */
    public boolean isSyslog() {
        return false;
    }

}
